package com.xiashitech.agent.instrumentation.biz.desensitization.httpurl.struts.inst;

import com.xiashitech.agent.cache.status.CacheStatus;
import com.xiashitech.agent.config.StartConfig;
import com.xiashitech.agent.dto.UserRequest;
import com.xiashitech.agent.instrumentation.biz.desensitization.httpurl.struts.XSActionInvocationSingletons;
import com.xiashitech.agent.instrumentation.bridge.Java8Bridge;
import com.xiashitech.agent.instrumentation.httprequest.XSHttpServletResponseWrapper;
import com.xiashitech.agent.instrumentation.utils.BaggageUtil;
import com.xiashitech.agent.utils.DurationPrinter;
import com.xiashitech.interfaces.agent.dto.AgentConfigDTO;
import io.opentelemetry.javaagent.bootstrap.CallDepth;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.context.Context;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.context.Scope;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.struts2.ServletActionContext;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;

public class XSActionInvocationInstrumentation implements TypeInstrumentation {

    @Override
    public ElementMatcher<ClassLoader> classLoaderOptimization() {
        return hasClassesNamed("com.opensymphony.xwork2.DefaultActionInvocation");
    }

    @Override
    public ElementMatcher<TypeDescription> typeMatcher() {
        return named("com.opensymphony.xwork2.DefaultActionInvocation");

    }

    @Override
    public void transform(TypeTransformer typeTransformer) {
        typeTransformer.applyAdviceToMethod(
                isMethod().and(named("invokeActionOnly")),
                this.getClass().getName() + "$ActionAdvice");
    }


    @SuppressWarnings("unused")
    public static class ActionAdvice {

        @Advice.OnMethodEnter(suppress = Throwable.class)
        public static void onEnter(
                @Advice.Local("otelCallDepth") CallDepth callDepth,
                @Advice.Local("otelContext") Context context,
                @Advice.Local("otelScope") Scope scope,
                @Advice.Local("otelBaggageScope") Scope baggageScope,
                @Advice.Local("user") UserRequest userRequest,
                @Advice.Local("desenseImpl") String desenseImpl) {
            DurationPrinter durationPrinter = new DurationPrinter("XSActionInvocationInstrumentation");
            try {
                if (CacheStatus.cannotRun()) {
                    return;
                }
                if(AgentConfigDTO.getConfig().getDesenseImpl() != null) {
                    desenseImpl = AgentConfigDTO.getConfig().getDesenseImpl().get(StartConfig.getParamValueFromKey(StartConfig.systemId));

                }
                if (desenseImpl == null || "".equals(desenseImpl) || !"strutsAction".equalsIgnoreCase(desenseImpl)){
                    return;
                }
                HttpServletRequest request = ServletActionContext.getRequest();
                HttpServletResponse response = ServletActionContext.getResponse();
                userRequest = UserRequest.builder().build();
                userRequest.setRequest(request);
                userRequest.setAnyObject(response);
                Context parentContext = currentContext();
                if (XSActionInvocationSingletons.instrumenter().shouldStart(parentContext, userRequest)) {
                    parentContext = BaggageUtil.syncBaggage().build().storeInContext(parentContext);
                    context = XSActionInvocationSingletons.instrumenter().start(parentContext, userRequest);
                    context = BaggageUtil.syncBaggage().build().storeInContext(context);
                    if (response instanceof XSHttpServletResponseWrapper){
                        context = Java8Bridge.currentForBaggage().toBuilder()
                                .put("endpoint-api", userRequest.getEndpointApi())
                                .put("api-systemId", StartConfig.getParamValueFromKey(StartConfig.systemId))
                                .build().storeInContext(context);
                    }
                    scope = context.makeCurrent();
                }
            } catch (Throwable e) {
                StartConfig.throwableStackPrinter(e);
            }
            durationPrinter.printElapseDuration("ActionAdvice b");
        }


        @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
        public static void onExit(@Advice.Local("otelCallDepth") CallDepth callDepth,
                                  @Advice.Local("otelContext") Context context,
                                  @Advice.Local("otelScope") Scope scope,
                                  @Advice.Local("user") UserRequest userRequest,
                                  @Advice.Local("desenseImpl") String desenseImpl) {
            DurationPrinter durationPrinter = new DurationPrinter("XSActionInvocationInstrumentation");
            try {
                if (CacheStatus.cannotRun()) {
                    return;
                }
                if (desenseImpl == null || "".equals(desenseImpl) || !"strutsAction".equalsIgnoreCase(desenseImpl)){
                    return;
                }
                HttpServletResponse response = ServletActionContext.getResponse();
                scope.close();
                XSActionInvocationSingletons.instrumenter().end(context, userRequest, (HttpServletResponse) response, null);
            } catch (Throwable e) {
                StartConfig.throwableStackPrinter(e);
            }
            durationPrinter.printElapseDuration("ActionAdvice e");
        }

    }

}
